package com.leyou.search.service.impl;

import com.leyou.common.exception.LyException;
import com.leyou.item.clients.ItemClient;
import com.leyou.search.dto.SearchDTO;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@Service
public class SearchServiceImpl implements SearchService {

    private final GoodsRepository goodsRepository;
    private final ItemClient itemClient;

    public SearchServiceImpl(GoodsRepository goodsRepository, ItemClient itemClient) {
        this.goodsRepository = goodsRepository;
        this.itemClient = itemClient;
    }

    @Override
    public Mono<List<String>> suggestion(String key) {


        return this.goodsRepository.suggestBySingleField("suggestion", key);
    }

    @Override
    public Mono<PageInfo<Goods>> list(SearchDTO searchDTO) {

        //武大，专门用来封装各种查询条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //核心查询条件
        QueryBuilder queryBuilder = builderQuery(searchDTO);

        sourceBuilder.query(queryBuilder);

        //TODO 高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<am>");
        highlightBuilder.postTags("</am>");

        sourceBuilder.highlighter(highlightBuilder);

        //排序，sortBy就是排序条件
        String sortBy = searchDTO.getSortBy();
        if (StringUtils.isNotBlank(sortBy)) {

            //true是表示desc是倒叙
            Boolean isDesc = searchDTO.getDesc();
            sourceBuilder.sort(SortBuilders.fieldSort(sortBy).order(isDesc ? SortOrder.DESC : SortOrder.ASC));

        }

        //分页
        int from = (searchDTO.getPage() - 1) * searchDTO.getRows();
        sourceBuilder.from(from);
        sourceBuilder.size(searchDTO.getRows());
        return this.goodsRepository.queryBySourceBuilderForPageHighlight(sourceBuilder);
    }

    @Override
    public Mono<Map<String, List<?>>> filter(SearchDTO searchDTO) {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //核心查询条件
        QueryBuilder queryBuilder = builderQuery(searchDTO);
        sourceBuilder.query(queryBuilder);

        //过滤条件和查询信息相关，如果查询变化，则过滤条件大概率也会发生改变
        sourceBuilder.query(queryBuilder);
        String brandAgg = "brandAgg";
        String categoryAgg = "categoryAgg";

        //添加品牌聚合
        sourceBuilder.aggregation(AggregationBuilders.terms(brandAgg).field("brandId").size(30));
        //添加分类集合
        sourceBuilder.aggregation(AggregationBuilders.terms(categoryAgg).field("categoryId").size(30));

        //添加其他规格参数的聚合，三层聚合，第一层nested，第二层根据规格参数名称，第三层根据规格参数的值
        sourceBuilder.aggregation(
                AggregationBuilders.nested("specAgg", "specs")
                        .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name").size(30)
                                .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value").size(30)))

        );

        //执行查询聚合
        Mono<Aggregations> aggregationsMono = this.goodsRepository.aggregationBySourceBuilder(sourceBuilder);

        //把直接聚合结果，处理成最终要的类型，如Map,es6,map,reduce

        return aggregationsMono.map(aggregations -> {

            Map<String, List<?>> result = new LinkedHashMap<>();

            Terms categoryTerms = aggregations.get(categoryAgg);
            List<Long> categoryIds = categoryTerms.getBuckets().stream().map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber().longValue()).collect(Collectors.toList());

            if (!CollectionUtils.isEmpty(categoryIds)) {

                result.put("分类", this.itemClient.listCategoryByIds(categoryIds));

            }

            //根据聚合名称获取聚合效果
            Terms brandTerms = aggregations.get(brandAgg);

            //获取聚合分桶
            List<Long> brandIds = brandTerms.getBuckets()
                    .stream()
                    .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber().longValue())
                    .collect(Collectors.toList());

            //查询品牌信息，一定要保证有id可查询
            if (!CollectionUtils.isEmpty(brandIds)) {
                result.put("品牌", this.itemClient.listBrandByIds(brandIds));
            }

            //解析第一层nested
            Nested specAgg = aggregations.get("specAgg");

            //解析第二层termsAgg
            Terms nameAgg = specAgg.getAggregations().get("nameAgg");

            nameAgg.getBuckets().forEach(bucket -> {
                //key就是规格参数的名称
                String key = ((Terms.Bucket) bucket).getKeyAsString();
                //解析第三层,根据聚合名称获取第三级聚合结果
                Terms valueTerms = ((Terms.Bucket) bucket).getAggregations().get("valueAgg");

                List<String> valueList = valueTerms.getBuckets()
                        .stream()
                        .map(bk -> bk.getKeyAsString())
                        .collect(Collectors.toList());

                result.put(key, valueList);

            });

            return result;
        });


    }


    private QueryBuilder builderQuery(SearchDTO searchDTO) {
        String key = searchDTO.getKey();
        if (StringUtils.isEmpty(key)) {
            throw new LyException(400, "查询条件不能为空");
        }

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.must(QueryBuilders.matchQuery("title", key).operator(Operator.AND));

        //获取所有的过滤条件
        Map<String, String> filters = searchDTO.getFilters();

        //获取有效的过滤条件，添加filter到queryBuilder中
        if (!CollectionUtils.isEmpty(filters)) {

            //entry的内容 eg: CPU品牌（paramKey）：海思（Hisilicon）（paramValue）|| 分类：76 || 品牌：8557
            filters.entrySet().forEach(entry -> {
                //可搜索规格参数名称
                String paramKey = entry.getKey();

                //可搜索规格参数的值
                String paramValue = entry.getValue();
                if (paramKey.equals("品牌")) {
                    queryBuilder.filter(QueryBuilders.termQuery("brandId", paramValue));
                } else if (paramKey.equals("分类")) {
                    queryBuilder.filter(QueryBuilders.termQuery("categoryId", paramValue));
                } else {

                    //给nested内部添加一个布尔查询
                    BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                    //查询条件分别为specs.name和specs.value相对应规格参数的名称和值
                    nestedBoolQuery.must(QueryBuilders.termQuery("specs.name", paramKey));
                    nestedBoolQuery.must(QueryBuilders.termQuery("specs.value", paramValue));
                    queryBuilder.filter(QueryBuilders.nestedQuery("specs", nestedBoolQuery, ScoreMode.None));

                }
            });
        }

        return queryBuilder;
    }
}
